#include "CDeviceManagerConfig.h"

#include <QFile>
#include <QString>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

#include "logger.h"
#include "zlbase/zlbase.h"

CDeviceManagerConfig::CDeviceManagerConfig()
{
}

CDeviceManagerConfig::~CDeviceManagerConfig()
{
}

bool CDeviceManagerConfig::Read(ZLDeviceConfig &config, const std::string &filename)
{
    try
    {
        LOG(INFO) << "start to parse xml file: " << filename;
        // open file
        QFile file(filename.c_str());
        auto ret = file.open(QIODevice::ReadOnly | QFile::Text);
        if (!ret)
        {
            LOG(ERROR) << "open xml config file failed, " << filename;
            return false;
        }

        // parse xml file
        QXmlStreamReader xml(&file);
        while (!xml.atEnd() && !xml.hasError())
        {
            auto type = xml.readNext();
            auto name = xml.name();
            switch (type)
            {
                case QXmlStreamReader::StartDocument:
                    break;
                case QXmlStreamReader::StartElement:
                    if (false)
                        break;
                    else if (name == "zlbase")
                        parse_zlbase_info(xml, config.zlbase_info);
                    else if (name == "peripherals")
                        parse_peripherals_info(xml, config.peripherals_info);
                    break;
                default:
                    break;
            }
        }
        file.close();
        LOG(INFO) << "parse xml file done";
        return true;
    }
    catch (const std::exception &e)
    {
        LOG(ERROR) << "parse xml file exception: " << e.what();
        return false;
    }
}

bool CDeviceManagerConfig::Write(const ZLDeviceConfig &config, const std::string &filename)
{
    try
    {
        LOG(INFO) << "start to write xml file: " << filename;
        QFile file(filename.c_str());
        auto ret = file.open(QIODevice::WriteOnly | QFile::Text);
        if (!ret)
        {
            LOG(ERROR) << "open xml file failed";
            return false;
        }

        QXmlStreamWriter xml(&file);
        xml.setAutoFormatting(true);
        xml.setCodec("utf-8");
        xml.writeStartDocument();
        // root <devices>
        xml.writeStartElement("devices");                          // <devices>
        stringify_zlbase_info(xml, config.zlbase_info);            // <zlbase>
        stringify_peripherals_info(xml, config.peripherals_info);  // <peripherals>
        xml.writeEndElement();                                     // </devices>
        xml.writeEndDocument();

        file.close();
        LOG(INFO) << "write xml file done";
        return true;
    }
    catch (const std::exception &e)
    {
        LOG(ERROR) << "write xml file failed: " << e.what() << '\n';
        return false;
    }
}

void CDeviceManagerConfig::parse_zlbase_info(QXmlStreamReader &xml, ZLBaseInfo &zlbase_info)
{
    LOG(INFO) << "+++parse zlbase info";
    while (!xml.atEnd() && !xml.hasError())
    {
        auto type = xml.readNext();
        auto name = xml.name();
        switch (type)
        {
            case QXmlStreamReader::StartElement:
                if (name == "version")
                {
                    zlbase_info.version = xml.readElementText().toStdString();
                }
                else if (name == "device")
                {
                    ZLBaseDevice device;
                    parse_zlbase_device(xml, device);
                    zlbase_info.devices.push_back(device);
                }
                break;
            case QXmlStreamReader::EndElement:
                if (name == "zlbase")
                {
                    LOG(INFO) << "---parse zlbase info done: " << zlbase_info.version;
                    return;
                }
                break;
            default:
                break;
        }
    }
}

void CDeviceManagerConfig::parse_zlbase_device(QXmlStreamReader &xml, ZLBaseDevice &device)
{
    auto parse_protocol = [&]() {
        LOG(INFO) << "parse device protocol";
        while (!xml.atEnd() && !xml.hasError())
        {
            auto type = xml.readNext();
            auto name = xml.name();
            switch (type)
            {
                case QXmlStreamReader::StartElement:
                    if (false)
                        break;
                    else if (name == "type")
                        device.protocol.type = xml.readElementText().toInt();
                    else if (name == "name")
                        device.protocol.name = xml.readElementText().toStdString();
                    else if (name == "uuid")
                        device.protocol.uuid = xml.readElementText().toStdString();
                    else if (name == "version")
                        device.protocol.version = xml.readElementText().toStdString();
                    break;
                case QXmlStreamReader::EndElement:
                    if (name == "protocol")
                    {
                        LOG(INFO) << "parse device protocol done";
                        return;
                    }

                default:
                    break;
            }
        }
    };
    auto parse_datalink = [&]() {
        LOG(INFO) << "parse device datalink";
        while (!xml.atEnd() && !xml.hasError())
        {
            auto type = xml.readNext();
            auto name = xml.name();
            switch (type)
            {
                case QXmlStreamReader::StartElement:
                    if (false)
                        break;
                    else if (name == "type")
                        device.datalink.type = xml.readElementText().toInt();
                    else if (name == "isshared")
                        device.datalink.is_shared = xml.readElementText().toInt() == 1 ? true : false;
                    // parse ethernet datalink options
                    else if (name == "localaddr")
                    {
                        auto tmp = xml.readElementText().toStdString();
                        strncpy(device.datalink.localaddr, tmp.c_str(), sizeof(device.datalink.localaddr));
                        break;
                    }
                    else if (name == "localport")
                        device.datalink.localport = xml.readElementText().toInt();
                    else if (name == "remoteaddr")
                    {
                        auto tmp = xml.readElementText().toStdString();
                        strncpy(device.datalink.remoteaddr, tmp.c_str(), sizeof(device.datalink.remoteaddr));
                    }
                    else if (name == "remoteport")
                        device.datalink.remoteport = xml.readElementText().toInt();
                    // parse serial port datalink options
                    else if (name == "com")
                    {
                        auto tmp = xml.readElementText().toStdString();
                        strncpy(device.datalink.com, tmp.c_str(), sizeof(device.datalink.com));
                    }
                    else if (name == "baudrate")
                        device.datalink.baud_rate = xml.readElementText().toInt();
                    else if (name == "flowcontrol")
                        device.datalink.flow_control = xml.readElementText().toInt();
                    else if (name == "parity")
                        device.datalink.parity = xml.readElementText().toInt();
                    else if (name == "stopbits")
                        device.datalink.stop_bits = xml.readElementText().toInt();
                    else if (name == "charactersize")
                        device.datalink.character_size = xml.readElementText().toInt();
                    break;
                case QXmlStreamReader::EndElement:
                    if (name == "datalink")
                    {
                        LOG(INFO) << "parse device datalink done";
                        return;
                    }
                default:
                    break;
            }
        }
    };

    LOG(INFO) << "parse zlbse device";
    while (!xml.atEnd() && !xml.hasError())
    {
        auto type = xml.readNext();
        auto name = xml.name();
        switch (type)
        {
            case QXmlStreamReader::StartElement:
                if (false)
                    break;
                else if (name == "id")
                    device.id = xml.readElementText().toStdString();
                else if (name == "type")
                    device.type = xml.readElementText().toInt();
                else if (name == "name")
                    device.name = xml.readElementText().toStdString();
                else if (name == "number")
                    device.number = xml.readElementText().toInt();
                else if (name == "enable_debuf")
                    device.enable_debug = xml.readElementText().toInt() == 0 ? false : true;
                // door contol board
                else if (name == "boxcount" && device.type == zl::zlbase::EDeviceType_DCB)
                    device.boxcount = xml.readElementText().toInt();
                // todo monitor control board
                // todo tempreature control board
                else if (name == "protocol")
                    parse_protocol();
                else if (name == "datalink")
                    parse_datalink();
                break;
            case QXmlStreamReader::EndElement:
                if (name == "device")
                {
                    LOG(INFO) << "parse zlbase device done";
                    return;
                }
                break;
            default:
                break;
        }
    }
}

void CDeviceManagerConfig::parse_peripherals_info(QXmlStreamReader &xml, ZLPeripheralsInfo &peri_info)
{
    // todo
}

void CDeviceManagerConfig::parse_peripherals_device(QXmlStreamReader &xml, ZLPeripheralsDevice &device)
{
    // todo
}

void CDeviceManagerConfig::stringify_zlbase_info(QXmlStreamWriter &xml, const ZLBaseInfo &zlbase_info)
{
    LOG(INFO) << "stringify zlbase info, " << zl::zlbase::ZLApiInstance::Version();
    xml.writeStartElement("zlbase");                                        // <zlbase>
    xml.writeTextElement("version", zl::zlbase::ZLApiInstance::Version());  // <version>

    // write devices
    for (const auto &d : zlbase_info.devices)
    {
        stringify_zlbase_device(xml, d);
    }

    xml.writeEndElement();  // </zlbase>
    LOG(INFO) << "stringify zlbase done";
}

void CDeviceManagerConfig::stringify_zlbase_device(QXmlStreamWriter &xml, const ZLBaseDevice &d)
{
    LOG(INFO) << "start stringify zlbase device";
    xml.writeStartElement("device");                                   // <device>
    xml.writeTextElement("id", d.id.c_str());                          // <id>
    xml.writeTextElement("type", QString::number(d.type));             // <type>
    xml.writeTextElement("name", d.name.c_str());                      // <name>
    xml.writeTextElement("number", QString::number(d.number));         // <number>
    xml.writeTextElement("enable_debug", d.enable_debug ? "1" : "0");  // <enable_debug>

    if (d.type == zl::zlbase::EDeviceType_DCB)
    {
        // door control board data
        xml.writeTextElement("boxcount", QString::number(d.boxcount));  // <boxcount>
    }
    else if (d.type == zl::zlbase::EDeviceType_MCB)
    {
        // todo monitor control board data
    }
    else if (d.type == zl::zlbase::EDeviceType_TCB)
    {
        // todo temperature control baord data
    }

    // protocol data
    xml.writeStartElement("protocol");                               // <protocol>
    xml.writeTextElement("type", QString::number(d.protocol.type));  // <type>
    xml.writeTextElement("name", d.protocol.name.c_str());           // <name>
    xml.writeTextElement("uuid", d.protocol.uuid.c_str());           // <uuid>
    xml.writeTextElement("version", d.protocol.version.c_str());     // <version>
    xml.writeEndElement();                                           // </protocol>

    // datalink data
    xml.writeStartElement("datalink");                                   // <datalink>
    xml.writeTextElement("type", QString::number(d.datalink.type));      // <type>
    xml.writeTextElement("isshared", d.datalink.is_shared ? "1" : "0");  // <isshared>
    // write ethernet datalink data
    if (d.datalink.type == zl::zlbase::EDatalinkType_EthernetUdp ||
        d.datalink.type == zl::zlbase::EDatalinkType_EthernetTcp)
    {
        xml.writeTextElement("localaddr", d.datalink.localaddr);                     // <localaddr>
        xml.writeTextElement("localport", QString::number(d.datalink.localport));    // <localport>
        xml.writeTextElement("remoteaddr", d.datalink.remoteaddr);                   // <remoteaddr>
        xml.writeTextElement("remoteport", QString::number(d.datalink.remoteport));  // <remoteport>
    }
    // write serial port datalink data
    if (d.datalink.type == zl::zlbase::EDatalinkType_Serialport)
    {
        xml.writeTextElement("com", d.datalink.com);                                        // <com>
        xml.writeTextElement("baudrate", QString::number(d.datalink.baud_rate));            // <baudrate>
        xml.writeTextElement("flowcontrol", QString::number(d.datalink.flow_control));      // <flowcontrol>
        xml.writeTextElement("parity", QString::number(d.datalink.parity));                 // <parity>
        xml.writeTextElement("stopbits", QString::number(d.datalink.stop_bits));            // <stopbits>
        xml.writeTextElement("charactersize", QString::number(d.datalink.character_size));  // <charactersize>
        xml.writeEndElement();                                                              // </datalink>
    }
    xml.writeEndElement();  // </datalink>
    xml.writeEndElement();  // </device>
    LOG(INFO) << "stringify zlbase device done";
}

void CDeviceManagerConfig::stringify_peripherals_info(QXmlStreamWriter &xml, const ZLPeripheralsInfo &peri_info)
{
    // todo
}

void CDeviceManagerConfig::stringify_peripherals_device(QXmlStreamWriter &xml, const ZLPeripheralsDevice &d)
{
    // todo
}